DATA BACKTESTING ANALYSIS PROJECT¶

(For Top 20 Stocks Listed on NSE India)¶

Author - Shubham Jangra¶


A Brief NOTE Before Starting This Project!!!¶

NSE India has discontinued old website which is used by NSEPY, check this link for reference:¶

(https://github.com/swapniljariwala/nsepy/issues/252)

Now we have new updated library nsepython instead of old nsepy library¶
I tried using old library but It didn't work, it wasn't fetching any data¶
I'm using the new updated library; install it with below command:¶

pip install nsepython

Importing Libraries¶

In [1]:
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from nsepython import *
from datetime import date, timedelta

Filtering Warnings¶

In [2]:
import warnings
warnings.filterwarnings('ignore')

Getting NSE Top 20 Gainers from official website. Click here to redirect:¶

https://www.nseindia.com/market-data/top-gainers-losers

In [3]:
# I've already downloaded data .csv file from NSE website
top20_nifty_stocks = pd.read_csv('T20-GL-gainers-NIFTY-26-Jul-2024.csv')
top20_nifty_stocks
Out[3]:
Symbol Open High Low Prev. Close LTP %chng Volume Value CA
0 SHRIRAMFIN 2705.80 3044.00 2690.00 2679.00 2934.00 9.52 6439265 1.850831e+10 23-Jul-2024
1 CIPLA 1504.75 1600.00 1501.00 1500.05 1586.50 5.76 4686806 7.282687e+09 02-Aug-2024
2 DIVISLAB 4570.95 4810.00 4563.85 4547.00 4792.25 5.39 1262056 5.979785e+09 02-Aug-2024
3 BHARTIARTL 1450.25 1520.00 1449.15 1449.15 1511.75 4.32 9563621 1.433788e+10 11-Aug-2023
4 APOLLOHOSP 6433.00 6679.95 6393.55 6385.80 6650.00 4.14 432670 2.851676e+09 20-Feb-2024
5 ADANIPORTS 1495.90 1547.90 1490.00 1487.00 1540.75 3.61 4500404 6.888228e+09 14-Jun-2024
6 WIPRO 510.00 528.55 508.50 506.85 524.50 3.48 13074509 6.808681e+09 16-Jul-2024
7 TATASTEEL 158.75 162.95 158.21 157.39 162.62 3.32 45636115 7.387574e+09 21-Jun-2024
8 ADANIENT 2995.00 3109.00 2988.10 2973.50 3072.25 3.32 1939775 5.948320e+09 14-Jun-2024
9 SUNPHARMA 1670.45 1723.80 1665.00 1665.80 1721.00 3.31 4591025 7.803090e+09 12-Jul-2024
10 SBILIFE 1686.25 1777.70 1686.25 1695.40 1750.60 3.26 2982225 5.216359e+09 15-Mar-2024
11 LTIM 5599.85 5811.90 5585.05 5597.90 5780.00 3.25 497156 2.861366e+09 19-Jun-2024
12 HCLTECH 1602.80 1640.00 1594.00 1587.60 1638.80 3.22 2813950 4.572641e+09 23-Jul-2024
13 HINDALCO 660.00 668.50 655.00 646.55 667.05 3.17 4730509 3.137510e+09 14-Aug-2023
14 INFY 1825.35 1883.00 1825.35 1824.85 1880.00 3.02 11368877 2.128117e+10 31-May-2024
15 HDFCLIFE 683.15 709.80 682.05 683.15 703.25 2.94 8932963 6.259774e+09 21-Jun-2024
16 COALINDIA 498.25 511.70 498.25 494.00 508.45 2.93 20437719 1.038359e+10 20-Feb-2024
17 JSWSTEEL 880.00 908.80 875.30 874.50 899.00 2.80 2056321 1.848900e+09 09-Jul-2024
18 M&M 2821.60 2897.40 2790.00 2811.40 2881.80 2.50 2352247 6.729379e+09 05-Jul-2024
19 ITC 494.05 506.40 487.40 489.95 501.55 2.37 20735172 1.033047e+10 04-Jun-2024

List of all Top 20 Gainers¶

In [4]:
top20_stocks = top20_nifty_stocks['Symbol'].values
top20_stocks
Out[4]:
array(['SHRIRAMFIN', 'CIPLA', 'DIVISLAB', 'BHARTIARTL', 'APOLLOHOSP',
       'ADANIPORTS', 'WIPRO', 'TATASTEEL', 'ADANIENT', 'SUNPHARMA',
       'SBILIFE', 'LTIM', 'HCLTECH', 'HINDALCO', 'INFY', 'HDFCLIFE',
       'COALINDIA', 'JSWSTEEL', 'M&M', 'ITC'], dtype=object)

Defining Starting Date(i.e. 5 years back from today) & Ending Date(i.e. today)¶

In [5]:
# Define the start and end dates for the 5 year period
end_date = date.today()
start_date = end_date - timedelta(days=5*365)
end_date = end_date.strftime('%d-%m-%Y')
start_date = start_date.strftime('%d-%m-%Y')
print('start_date:', start_date)
print('end_date:', end_date)
start_date: 29-07-2019
end_date: 27-07-2024

PRIOR NOTE:¶

First I'm implementing a backtesting system for a single stock to see if everything is working perfectly.
Then I'm going to do it for all 20 top stocks. *The question is:*

Why I'm doing it:¶

As to create a merged dataframe which consists of all 20 stocks, the process requires very high computational power.
I already tried several times doing this, but I'm unable to process the code for the above mentioned problem.

So, What's the Solution?¶

The solution is streamlit app which is deployed online, you can check any stock analysis.
You can even download detailed interactive visualization!


1. Data Acquisition and Preparation¶

In [6]:
def get_stock_data(stock_name: str, start_date=start_date, end_date=end_date) -> pd.DataFrame:
    """
    Retrieves historical stock data for the specified stock within the given date range(Default Date Range - 5 Years from today).

    Args:
        stock_name (str): The symbol of the stock for which to retrieve data.
        start_date (str, optional): The starting date of the date range in the format 'DD-MM-YYYY'. Defaults to the global `start_date` variable.
        end_date (str, optional): The ending date of the date range in the format 'DD-MM-YYYY'. Defaults to the global `end_date` variable.

    Returns:
        pd.DataFrame: A DataFrame containing the historical stock data with the following columns:
            - Symbol: The stock symbol
            - Date: The stock date records
            - Year: The particular year
            - High: The daily high price
            - Low: The daily low price
            - Open: The daily opening price
            - Close: The daily closing price
            - Volume: The daily trading volume
    """
    # Generating Stock Data with `nsepython` library by providing symbol, start date & end date
    # Values sorted ascending by 'CH_TIMESTAMP' column (i.e. by Date: start to end)
    stock_data_raw = equity_history(stock_name, 'EQ', start_date, end_date)
    # Selecting Important Column for our Analysis
    important_cols = ['CH_SYMBOL', 'CH_TIMESTAMP', 'CH_TRADE_HIGH_PRICE', 'CH_TRADE_LOW_PRICE', 'CH_OPENING_PRICE', 'CH_CLOSING_PRICE', 'CH_TOT_TRADED_QTY']
    # Sub-setting Stock Data with important columns only
    stock_data = stock_data_raw[important_cols]
    # Renaming columns
    stock_data = stock_data.rename(columns={
        'CH_SYMBOL': 'Symbol',
        'CH_TIMESTAMP': 'Date',
        'CH_TRADE_HIGH_PRICE': 'High',
        'CH_TRADE_LOW_PRICE': 'Low',
        'CH_OPENING_PRICE': 'Open',
        'CH_CLOSING_PRICE': 'Close',
        'CH_TOT_TRADED_QTY': 'Volume'
    })
    stock_data['Date'] = pd.to_datetime(stock_data['Date'])
    stock_data['Year'] = stock_data['Date'].dt.year
    stock_data = stock_data[['Symbol','Date','Year','Low','High','Open','Close','Volume']].sort_values(by='Date')
    return stock_data
In [7]:
# Taking only the top stock for now; symbol named "SHRIRAMFIN"
SHRIRAMFIN = get_stock_data('SHRIRAMFIN')
SHRIRAMFIN
Out[7]:
Symbol Date Year Low High Open Close Volume
1257 SRTRANSFIN 2019-07-29 2019 962.55 990.00 980.80 967.45 1417626
1258 SRTRANSFIN 2019-07-30 2019 964.10 982.40 967.50 969.00 1468107
1259 SRTRANSFIN 2019-07-31 2019 959.00 975.45 967.85 969.25 1528948
1260 SRTRANSFIN 2019-08-01 2019 963.30 984.70 969.00 967.35 1274941
1261 SRTRANSFIN 2019-08-02 2019 960.00 981.90 971.90 978.45 1315470
... ... ... ... ... ... ... ... ...
13 SHRIRAMFIN 2024-07-22 2024 2764.05 2847.95 2799.90 2827.40 543557
14 SHRIRAMFIN 2024-07-23 2024 2663.40 2827.00 2827.00 2739.20 2445493
15 SHRIRAMFIN 2024-07-24 2024 2665.30 2760.00 2739.00 2724.25 1026115
16 SHRIRAMFIN 2024-07-25 2024 2635.50 2710.00 2699.50 2679.00 2089082
17 SHRIRAMFIN 2024-07-26 2024 2690.00 3044.00 2705.80 2925.00 6439336

1284 rows × 8 columns

2. Implementing a Simple Moving Average(SMA) Crossover Strategy¶

In [8]:
def simple_moving_average_crossover(df):
    """
    Implement a simple moving average crossover strategy.

    Parameters:
    df (pandas DataFrame): The historical data for a stock.

    Returns:
    pandas DataFrame: The backtested data with additional columns for strategy signals.
    """
    # Calculate the 50-day and 200-day simple moving averages
    df['50d'] = df['Close'].rolling(window=50).mean()
    df['200d'] = df['Close'].rolling(window=200).mean()

    # Generate buy/sell signals
    df['Signal'] = 0.0
    # Signal Value 1 depicts Buy Signal; 0 depicts Sell Signal
    df['Signal'] = df['50d'] > df['200d']  # True if 50d > 200d, else False
    df['Signal'] = df['Signal'].astype(int)  # Convert boolean to int (1 for buy, 0 for sell)
    
    # Buy Signal: When 50SMA line cross above 200SMA line, indicating Bull Market Sign
    # Bear Signal: When 50SMA line cross below 200SMA line, indicating Bear Market Sign

    # Create a column for entry/exit signals
    df['Entry/Exit'] = df['Signal'].diff()

    # Calculate strategy returns based on entry signals
    df['Strategy Returns'] = df['Close'].pct_change() * df['Signal'].shift(1)  # Use previous signal for returns
    df['Cumulative Strategy Returns'] = (1 + df['Strategy Returns']).cumprod() - 1  # Cumulative returns

    return df
In [9]:
simple_moving_average_crossover(SHRIRAMFIN)
Out[9]:
Symbol Date Year Low High Open Close Volume 50d 200d Signal Entry/Exit Strategy Returns Cumulative Strategy Returns
1257 SRTRANSFIN 2019-07-29 2019 962.55 990.00 980.80 967.45 1417626 NaN NaN 0 NaN NaN NaN
1258 SRTRANSFIN 2019-07-30 2019 964.10 982.40 967.50 969.00 1468107 NaN NaN 0 0.0 0.000000 0.000000
1259 SRTRANSFIN 2019-07-31 2019 959.00 975.45 967.85 969.25 1528948 NaN NaN 0 0.0 0.000000 0.000000
1260 SRTRANSFIN 2019-08-01 2019 963.30 984.70 969.00 967.35 1274941 NaN NaN 0 0.0 -0.000000 0.000000
1261 SRTRANSFIN 2019-08-02 2019 960.00 981.90 971.90 978.45 1315470 NaN NaN 0 0.0 0.000000 0.000000
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
13 SHRIRAMFIN 2024-07-22 2024 2764.05 2847.95 2799.90 2827.40 543557 2636.055 2325.20975 1 0.0 0.005888 0.815245
14 SHRIRAMFIN 2024-07-23 2024 2663.40 2827.00 2827.00 2739.20 2445493 2644.232 2329.80125 1 0.0 -0.031195 0.758619
15 SHRIRAMFIN 2024-07-24 2024 2665.30 2760.00 2739.00 2724.25 1026115 2652.317 2333.95825 1 0.0 -0.005458 0.749020
16 SHRIRAMFIN 2024-07-25 2024 2635.50 2710.00 2699.50 2679.00 2089082 2659.123 2337.86025 1 0.0 -0.016610 0.719969
17 SHRIRAMFIN 2024-07-26 2024 2690.00 3044.00 2705.80 2925.00 6439336 2670.191 2343.09475 1 0.0 0.091825 0.877906

1284 rows × 14 columns

3. Calculating Performance Metrics:¶

  • Total returns
  • Annualized returns
  • Maximum drawdown
  • Sharpe ratio
  • Win/Loss ratio
  • Number of trades executed
In [10]:
def calculate_performance_metrics(df):
    """
    Calculate key performance metrics for the backtested data.

    Parameters:
    df (pandas DataFrame): The backtested data.

    Returns:
    pandas DataFrame: The backtested data with additional columns for performance metrics.
    """
    # Calculate total returns
    df['Total Returns'] = df['Strategy Returns'].cumprod() - 1

    # Calculate annualized returns
    # Assuming daily returns, we can calculate annualized returns as follows:
    trading_days = 252  # Typical number of trading days in a year
    df['Annualized Returns'] = (1 + df['Total Returns']) ** (trading_days / len(df)) - 1

    # Calculate maximum drawdown
    df['High Watermark'] = df['Strategy Returns'].cummax()
    df['Drawdown'] = df['High Watermark'] - df['Strategy Returns']
    df['Max Drawdown'] = df['Drawdown'].max()

    # Calculate Sharpe ratio
    risk_free_rate = 0.0  # Assuming a risk-free rate of 0 for simplicity
    df['Sharpe Ratio'] = (df['Annualized Returns'] - risk_free_rate) / df['Strategy Returns'].std() * np.sqrt(trading_days)

    # Calculate win/loss ratio
    wins = df['Strategy Returns'][df['Strategy Returns'] > 0].count()
    losses = df['Strategy Returns'][df['Strategy Returns'] <= 0].count()
    df['Win/Loss Ratio'] = wins / losses if losses > 0 else wins

    # Calculate number of trades executed
    df['Number of Trades'] = df['Signal'].diff().ne(0).sum()
    
    return df
In [11]:
# Apply the performance metrics calculation
calculate_performance_metrics(SHRIRAMFIN)
Out[11]:
Symbol Date Year Low High Open Close Volume 50d 200d ... Strategy Returns Cumulative Strategy Returns Total Returns Annualized Returns High Watermark Drawdown Max Drawdown Sharpe Ratio Win/Loss Ratio Number of Trades
1257 SRTRANSFIN 2019-07-29 2019 962.55 990.00 980.80 967.45 1417626 NaN NaN ... NaN NaN NaN NaN NaN NaN 0.27702 NaN 0.394565 8
1258 SRTRANSFIN 2019-07-30 2019 964.10 982.40 967.50 969.00 1468107 NaN NaN ... 0.000000 0.000000 -1.0 -1.0 0.000000 0.000000 0.27702 -908.245604 0.394565 8
1259 SRTRANSFIN 2019-07-31 2019 959.00 975.45 967.85 969.25 1528948 NaN NaN ... 0.000000 0.000000 -1.0 -1.0 0.000000 0.000000 0.27702 -908.245604 0.394565 8
1260 SRTRANSFIN 2019-08-01 2019 963.30 984.70 969.00 967.35 1274941 NaN NaN ... -0.000000 0.000000 -1.0 -1.0 -0.000000 0.000000 0.27702 -908.245604 0.394565 8
1261 SRTRANSFIN 2019-08-02 2019 960.00 981.90 971.90 978.45 1315470 NaN NaN ... 0.000000 0.000000 -1.0 -1.0 0.000000 0.000000 0.27702 -908.245604 0.394565 8
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
13 SHRIRAMFIN 2024-07-22 2024 2764.05 2847.95 2799.90 2827.40 543557 2636.055 2325.20975 ... 0.005888 0.815245 -1.0 -1.0 0.162227 0.156339 0.27702 -908.245604 0.394565 8
14 SHRIRAMFIN 2024-07-23 2024 2663.40 2827.00 2827.00 2739.20 2445493 2644.232 2329.80125 ... -0.031195 0.758619 -1.0 -1.0 0.162227 0.193421 0.27702 -908.245604 0.394565 8
15 SHRIRAMFIN 2024-07-24 2024 2665.30 2760.00 2739.00 2724.25 1026115 2652.317 2333.95825 ... -0.005458 0.749020 -1.0 -1.0 0.162227 0.167684 0.27702 -908.245604 0.394565 8
16 SHRIRAMFIN 2024-07-25 2024 2635.50 2710.00 2699.50 2679.00 2089082 2659.123 2337.86025 ... -0.016610 0.719969 -1.0 -1.0 0.162227 0.178837 0.27702 -908.245604 0.394565 8
17 SHRIRAMFIN 2024-07-26 2024 2690.00 3044.00 2705.80 2925.00 6439336 2670.191 2343.09475 ... 0.091825 0.877906 -1.0 -1.0 0.162227 0.070401 0.27702 -908.245604 0.394565 8

1284 rows × 22 columns

4. Analysis and Insights:¶

  • Visualize the backtesting results using charts
  • Analyze the results, identifying strengths and weaknesses of the strategy.
  • Discuss how market conditions (e.g., bull vs. bear markets) affected the strategy's performance.
  • Suggest potential improvements or modifications to the strategy based on your analysis.

Visualize Backtesting Results:¶

In [12]:
def plot_backtested_data(backtested_data: pd.DataFrame, stock_name: str):
    df = backtested_data

    # Create the tooltip text with labels for candlestick
    df['tooltip'] = (
            'Date: ' + df['Date'].dt.strftime('%Y-%m-%d') + '<br>' +
            'Open: ' + df['Open'].astype(str) + '<br>' +
            'Low: ' + df['Low'].astype(str) + '<br>' +
            'Close: ' + df['Close'].astype(str) + '<br>' +
            'High: ' + df['High'].astype(str) + '<br>' +
            'Volume: ' + df['Volume'].astype(str)
    )

    # Create the candlestick trace
    candlestick = go.Candlestick(
        x=df['Date'],  # Change x-axis to df['Date']
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        name='Candlestick',
        hovertext=df['tooltip'],  # Use the custom tooltip
        hoverinfo='text'  # Show custom tooltip
    )

    # Create the 50-day SMA trace
    sma50 = go.Scatter(
        x=df['Date'],  # Change x-axis to df['Date']
        y=df['50d'],
        mode='lines',
        name='50-day SMA',
        line=dict(color='darkorchid')
    )

    # Create the 200-day SMA trace
    sma200 = go.Scatter(
        x=df['Date'],  # Change x-axis to df['Date']
        y=df['200d'],
        mode='lines',
        name='200-day SMA',
        line=dict(color='violet')
    )

    # Create the buy signal trace with tooltip
    buy_signals = df[df['Entry/Exit'] == 1]
    buy_trace = go.Scatter(
        x=buy_signals['Date'],  # Change x-axis to df['Date']
        y=buy_signals['Close'],
        mode='markers',
        marker=dict(
            symbol='triangle-up',
            size=25,
            color='green'
        ),
        name='Buy Signal',
        hovertext=(
                'Date: ' + buy_signals['Date'].dt.strftime('%Y-%m-%d') + '<br>' +
                'Close: ' + buy_signals['Close'].astype(str) + '<br>' +
                'Signal: Buy'
        ),
        hoverinfo='text'  # Show custom tooltip
    )

    # Create the sell signal trace with tooltip
    sell_signals = df[df['Entry/Exit'] == -1]
    sell_trace = go.Scatter(
        x=sell_signals['Date'],  # Change x-axis to df['Date']
        y=sell_signals['Close'],
        mode='markers',
        marker=dict(
            symbol='triangle-down',
            size=25,
            color='red'
        ),
        name='Sell Signal',
        hovertext=(
                'Date: ' + sell_signals['Date'].dt.strftime('%Y-%m-%d') + '<br>' +
                'Close: ' + sell_signals['Close'].astype(str) + '<br>' +
                'Signal: Sell'
        ),
        hoverinfo='text'  # Show custom tooltip
    )

    # Create the layout with quarterly ticks
    layout = go.Layout(
        title=f"{stock_name} - Simple Moving Average Crossover Strategy",
        xaxis_title="Date",
        yaxis_title="Price",
        xaxis_rangeslider_visible=False,
        width=1700,
        height=900,
        xaxis=dict(
            constrain='domain',
            range=[df['Date'].min() - pd.Timedelta(days=15), df['Date'].max() + pd.Timedelta(days=15)],  # Add padding
            tickvals=pd.date_range(start=df['Date'].min(), end=df['Date'].max(), freq='Q'),  # Quarterly ticks
            ticktext=pd.date_range(start=df['Date'].min(), end=df['Date'].max(), freq='Q').strftime('%Y-%m')  # Format ticks as Year-Month
        )
    )
    
    # Create the figure and plot
    fig = go.Figure(data=[candlestick, sma50, sma200, buy_trace, sell_trace], layout=layout)
    fig.update_layout(template='plotly_white')
    fig.show()
In [13]:
plot_backtested_data(SHRIRAMFIN, 'SHRIRAMFIN')

Breakdown of Performance Metrics:¶

In [14]:
# Breakdown of performance metrics on a yearly basis
def get_yearly_performance_metrics(backtested_data):
    yearly_performance = backtested_data.groupby('Year').agg({
        'Total Returns': ['mean', 'std'],
        'Annualized Returns': ['mean', 'std'],
        'Max Drawdown': ['mean', 'std'],
        'Sharpe Ratio': ['mean', 'std'],
        'Win/Loss Ratio': ['mean', 'std'],
        'Number of Trades': ['mean', 'std']
    })
    
    # Flatten the multi-level column index
    yearly_performance.columns = ['_'.join(col).strip() for col in yearly_performance.columns.values]
    
    # Reset the index to convert the aggregated data to a DataFrame
    yearly_performance = yearly_performance.reset_index()
    
    # Rename the columns for clarity
    yearly_performance.columns = ['Year'] + [col for col in yearly_performance.columns if col != 'Year']
    
    # Returning Dataframe
    return yearly_performance
In [15]:
# Outputting performance metrics on a yearly basis
get_yearly_performance_metrics(SHRIRAMFIN)
Out[15]:
Year Total Returns_mean Total Returns_std Annualized Returns_mean Annualized Returns_std Max Drawdown_mean Max Drawdown_std Sharpe Ratio_mean Sharpe Ratio_std Win/Loss Ratio_mean Win/Loss Ratio_std Number of Trades_mean Number of Trades_std
0 2019 -1.0 0.0 -1.0 0.0 0.27702 0.0 -908.245604 0.0 0.394565 0.0 8.0 0.0
1 2020 -1.0 0.0 -1.0 0.0 0.27702 0.0 -908.245604 0.0 0.394565 0.0 8.0 0.0
2 2021 -1.0 0.0 -1.0 0.0 0.27702 0.0 -908.245604 0.0 0.394565 0.0 8.0 0.0
3 2022 -1.0 0.0 -1.0 0.0 0.27702 0.0 -908.245604 0.0 0.394565 0.0 8.0 0.0
4 2023 -1.0 0.0 -1.0 0.0 0.27702 0.0 -908.245604 0.0 0.394565 0.0 8.0 0.0
5 2024 -1.0 0.0 -1.0 0.0 0.27702 0.0 -908.245604 0.0 0.394565 0.0 8.0 0.0

SHRIRAMFIN - Simple Moving Average Crossover Strategy Analysis¶

This chart depicts the price action of SHRIRAMFIN along with two simple moving averages (SMAs): a 50-day SMA and a 200-day SMA. This is a common technical analysis tool for identifying potential buy and sell signals.¶

Observations:¶

  • Candlestick: The green candlestick represents an upward movement in price, while the red candlestick represents a downward movement in price.
  • 50-day SMA: This SMA is a shorter-term indicator, showing the average price over the last 50 days.
  • 200-day SMA: This SMA is a longer-term indicator, showing the average price over the last 200 days.
  • Buy Signal: The green triangle indicates a buy signal. It is triggered when the 50-day SMA crosses above the 200-day SMA.
  • Sell Signal: The red triangle indicates a sell signal. It is triggered when the 50-day SMA crosses below the 200-day SMA.

Analysis:¶

  • Overall Trend: The chart shows a generally upward trend in the price of SHRIRAMFIN.
  • Bullish Momentum: The 50-day SMA has crossed above the 200-day SMA on multiple occasions. This suggests that there is bullish momentum in the stock.
  • Potential Buy Opportunities: The green triangles identify potential buy opportunities where the 50-day SMA crossed above the 200-day SMA, indicating a bullish crossover.
  • Potential Sell Opportunities: The red triangles identify potential sell opportunities where the 50-day SMA crossed below the 200-day SMA, indicating a bearish crossover.

Conclusion:¶

The simple moving average crossover strategy is a useful tool for identifying potential buy and sell opportunities in SHRIRAMFIN. However, it is important to note that this is just one indicator and should be used in conjunction with other technical and fundamental analysis.


Easy BackTesting Analysis using backtesting python library¶

Getting Same Stock Data¶

In [16]:
data = get_stock_data('SHRIRAMFIN')
data
Out[16]:
Symbol Date Year Low High Open Close Volume
1257 SRTRANSFIN 2019-07-29 2019 962.55 990.00 980.80 967.45 1417626
1258 SRTRANSFIN 2019-07-30 2019 964.10 982.40 967.50 969.00 1468107
1259 SRTRANSFIN 2019-07-31 2019 959.00 975.45 967.85 969.25 1528948
1260 SRTRANSFIN 2019-08-01 2019 963.30 984.70 969.00 967.35 1274941
1261 SRTRANSFIN 2019-08-02 2019 960.00 981.90 971.90 978.45 1315470
... ... ... ... ... ... ... ... ...
13 SHRIRAMFIN 2024-07-22 2024 2764.05 2847.95 2799.90 2827.40 543557
14 SHRIRAMFIN 2024-07-23 2024 2663.40 2827.00 2827.00 2739.20 2445493
15 SHRIRAMFIN 2024-07-24 2024 2665.30 2760.00 2739.00 2724.25 1026115
16 SHRIRAMFIN 2024-07-25 2024 2635.50 2710.00 2699.50 2679.00 2089082
17 SHRIRAMFIN 2024-07-26 2024 2690.00 3044.00 2705.80 2925.00 6439336

1284 rows × 8 columns

Making a class for Simple Moving Average Cross Strategy¶

In [17]:
from backtesting import Backtest, Strategy
from backtesting.lib import crossover
from backtesting.test import SMA
import pandas as pd

class SMACross(Strategy):
    def init(self):
        # Define two moving averages
        self.sma50 = self.I(SMA, self.data.Close, 50)  # Short-term MA
        self.sma200 = self.I(SMA, self.data.Close, 200)  # Long-term MA

    def next(self):
        # Buy when the short-term MA crosses above the long-term MA
        if crossover(self.sma50, self.sma200):
            self.buy()
        # Sell when the short-term MA crosses below the long-term MA
        elif crossover(self.sma200, self.sma50):
            self.sell()
Loading BokehJS ...

Printing Key Metrics(Stats)¶

In [18]:
backtest = Backtest(data, SMACross, commission=.002, exclusive_orders=True)
stats = backtest.run()
print(stats)
Start                                     0.0
End                                    1283.0
Duration                               1283.0
Exposure Time [%]                   61.137072
Equity Final [$]                    3065.8235
Equity Peak [$]                     10123.324
Return [%]                         -69.341765
Buy & Hold Return [%]              -64.781054
Return (Ann.) [%]                         0.0
Volatility (Ann.) [%]                     NaN
Sharpe Ratio                              NaN
Sortino Ratio                             NaN
Calmar Ratio                              0.0
Max. Drawdown [%]                  -79.137332
Avg. Drawdown [%]                  -79.137332
Max. Drawdown Duration                  784.0
Avg. Drawdown Duration                  784.0
# Trades                                  6.0
Win Rate [%]                              0.0
Best Trade [%]                      -0.350177
Worst Trade [%]                    -43.687348
Avg. Trade [%]                     -20.744538
Max. Trade Duration                     235.0
Avg. Trade Duration                130.666667
Profit Factor                             0.0
Expectancy [%]                     -19.261707
SQN                                 -2.094035
_strategy                            SMACross
_equity_curve                       Equity...
_trades                      Size  EntryBa...
dtype: object

Visualizing BackTesting Strategy¶

In [19]:
backtest.plot()
Out[19]:
GridPlot(
id = 'p1301', …)
align = 'auto',
aspect_ratio = None,
children = [(figure(id='p1044', ...), 0, 0), (figure(id='p1141', ...), 1, 0), (figure(id='p1002', ...), 2, 0), (figure(id='p1196', ...), 3, 0)],
cols = None,
context_menu = None,
css_classes = [],
disabled = False,
flow_mode = 'block',
height = None,
height_policy = 'auto',
js_event_callbacks = {},
js_property_callbacks = {},
margin = None,
max_height = None,
max_width = None,
min_height = None,
min_width = None,
name = None,
resizable = False,
rows = None,
sizing_mode = 'stretch_width',
spacing = 0,
styles = {},
stylesheets = [],
subscribed_events = PropertyValueSet(),
syncable = True,
tags = [],
toolbar = Toolbar(id='p1300', ...),
toolbar_location = 'right',
visible = True,
width = None,
width_policy = 'auto')

Creating Streamlit App¶

Find the code in stock-analysis-streamlit-app.py¶


END OF CODE¶